Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

go[2]-数据结构

参考golang手册,记录数据类型相关资料;方便日后来回顾基础知识。

基本数据

布尔

布尔类型,表示真与假。true|false

整型/浮点数

数值类型,分为整形和浮点类型,下面列举的是类型,以及读数的区间。

1
2
3
4
5
6
7
8
9
10
11
12
uint8       the set of all unsigned  8-bit integers (0 to 255)
uint16 the set of all unsigned 16-bit integers (0 to 65535)
uint32 the set of all unsigned 32-bit integers (0 to 4294967295)
uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8 the set of all signed 8-bit integers (-128 to 127)
int16 the set of all signed 16-bit integers (-32768 to 32767)
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

byte alias for uint8
rune alias for int32

整数类型按照字节数来分类,有8位、16位、32位、64位。每个数字的区间范围、内存空间都不一样。

int,uint会按照cpu字节大小最终是32位或者64位。Unicode.rune和int32等价,byte和uint8等价。

unsafe包里面提供的uintptr,不会规定内存的size,但是能容纳从c语言传递过来的内存指针。

整数

最高位使用了反码方式来表示。拿8位来举例子。他的取值范围是(-128 to 127)

-128= 二进制 1111 1111

127= 二进制 0111 1111

对有符号数字做位操作时,向右移将会自动补上符号位,向左移符号位不会受到影响。

1
2
3
4
5
6
7
8
9
    var a int8
a = -1
fmt.Printf("%08b\n", a)
a = a >> 2
fmt.Printf("%08b\n", a)
a = a << 3
fmt.Printf("%08b\n", a)

math.MinInt8
1
2
3
-0000001
-0000001
-0001000

这个特性需要注意,一般在操作位的时候,我们都偏向使用无符号的数字来处理。

在处理循环的时候,最好推荐使用有符号的变量来处理。防止程序出现问题。

golang-int256-类型变量问题

原始的数据:

“0xffffffffffffffffffffffffffffffffffffffffffffffffbc705c3c8afed1d”

转换成 int256 :

-4868289782087757360

通过搜索 golang int256 发现了这个资料

parse negetive int256

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
func t1() {
// 32字节的内存块,以16进制字符串表示
hexData := "fffffffffffffffffffffffffffffffffffffffffe9d5f1ea55335b5"

// 将16进制字符串解析为big.Int类型的整数
intVal := new(big.Int)
intVal, success := intVal.SetString(hexData, 16)

if !success {
fmt.Println("Failed to parse hex data.")
return
}

// 将big.Int类型的整数转换为有符号的int64
int64Val := intVal.Int64()

fmt.Printf("Parsed integer: %d\n", int64Val)
}

func decode(data []byte, scale int) (out *big.Int, err error) {
out = new(big.Int).SetBytes(data)

// Check if negative:
if len(data) > 0 && data[0]&0x80 != 0 {
// It's negative.
// Convert 2's complement negative to abs big-endian:
data2 := make([]byte, len(data)+1)
data2[0] = 1
temp := new(big.Int).SetBytes(data2)
out.Sub(temp, out)

// Apply negative sign:
out.Neg(out)
}

// Apply scale:
if scale > 0 {
temp, _ := new(big.Int).SetString("1"+strings.Repeat("0", scale), 10)
out.Div(out, temp)
}

return
}

无符号

将会把全部的bit位当成数值来计算。在做位操作时,可以当成数组来理解。

整数类型的运算优先级(行里面是级别相等,从上往下的列,级别是依次降低):

1
2
3
4
5
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||

浮点类型

1
2
3
4
5
6
7
8

float32 the set of all IEEE-754 32-bit floating-point numbers
float64 the set of all IEEE-754 64-bit floating-point numbers

// 复数
complex64 the set of all complex numbers with float32 real and imaginary parts
complex128 the set of all complex numbers with float64 real and imaginary parts

提供了 float32 float64 两种。IEEE 754标准。

所有极限值被定义在math包中。

1
math.MaxFloat32

float32 大约能理解成6个十进制的精度;float64能到15个十进制精度。

时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 将字符串转换成时间
// 2006-01-02 15:04:05
// 指定时区,否则将会存在问题
var timeZone = "Asia/Shanghai"
func String2Time(str string) (time.Time, error) {
loc, _ := time.LoadLocation(timeZone)
dateTime, err := time.ParseInLocation("2006-01-02 15:04:05", str, loc)
if err != nil {
logrus.Println("Error:", err)
return time.Now(), err
}
return dateTime, nil
}

// 计算已经流逝的时间。
func GetLocalEscapeTime(inputTime time.Time) time.Duration {

h := time.Duration(inputTime.Local().Hour()) * time.Hour
m := time.Duration(inputTime.Minute()) * time.Minute
s := time.Duration(inputTime.Second()) * time.Second

return h + m + s
}

// 按照时间对齐
func AlginTime(inputTime time.Time, durationTime time.Duration) (time.Time, time.Time) {

if durationTime == oneDay {

gap := GetLocalEscapeTime(inputTime)
startTime := inputTime.Add(-gap)
endTime := startTime.Add(oneDay)
return startTime, endTime
}

if durationTime == oneWeek {
gap := GetLocalEscapeTime(inputTime)
startTime := inputTime.Add(-gap)
// the week (Sunday = 0, ...).
weekday := int(inputTime.Local().Weekday())
// 周日为0,我们想计算出周一时间;
if weekday > 0 {
startTime = startTime.Add(-oneDay * (time.Duration(weekday - 1)))
} else {
startTime = inputTime.Add(-oneDay * 6)
}
endTime := startTime.Add(oneDay * 7)
logrus.Infoln(inputTime, startTime, endTime)
return startTime, endTime
}

startTime := inputTime.Truncate(durationTime)
endTime := startTime.Add(durationTime)

if durationTime == oneDay {
startTime = startTime.Add(-cstTimeZone)
endTime = endTime.Add(-cstTimeZone)
logrus.Infoln(inputTime, startTime, endTime)
return startTime, endTime
} else {
logrus.Infoln(inputTime, startTime, endTime)
return startTime, endTime
}

}

// 测试代码
func TestUtilsTime(t *testing.T) {
logrus.Debugln("start TestUtilsTime")
t1str := "2023-09-14 22:54:59"

t1, err := utils_string.String2Time(t1str)
if err != nil {
logrus.Errorln(err.Error())
return
}

m1, err := time.ParseDuration("1m")
AlginTime(t1, m1)
m5, err := time.ParseDuration("5m")
AlginTime(t1, m5)
m15, err := time.ParseDuration("15m")
AlginTime(t1, m15)
m30, err := time.ParseDuration("30m")
AlginTime(t1, m30)
h1, err := time.ParseDuration("1h")
AlginTime(t1, h1)
h2, err := time.ParseDuration("2h")
AlginTime(t1, h2)
h4, err := time.ParseDuration("4h")
AlginTime(t1, h4)

}

// 获取时间戳
// https://stackoverflow.com/questions/34975007/how-can-i-extract-the-value-of-my-current-local-time-offset
package main

import (
"fmt"
"time"
)

func main() {
t := time.Now()
zone, offset := t.Zone()
fmt.Println(zone, offset)
}

// 时间比较的时候,还是尽量通过 自己匹配的级别精度来做比较,否则会出现一些问题

设置时区

https://stackoverflow.com/questions/27991671/how-to-get-the-current-timestamp-in-other-timezones-in-golang

1
2
3
4
5
//init the loc
loc, _ := time.LoadLocation("Asia/Shanghai")

//set timezone,
now := time.Now().In(loc)

将字符串转换成时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func main() {
// 给定的时间字符串
timeString := "2024-02-15T07:20:43.000+00:00"

// 定义日期时间格式
layout := "2006-01-02T15:04:05.000Z07:00"

// 解析时间字符串
parsedTime, err := time.Parse(layout, timeString)
if err != nil {
fmt.Println("Error parsing time:", err)
return
}

// 打印解析后的时间
fmt.Println("Parsed Time:", parsedTime)
}

String

string存储了一系列的bytes,string的字节数是就是字符串的长度,而且不可能为负数。当创建了字符串之后,将不会发生改变。可以使用内建函数len取得字符串的长度。可以通过下标方式取得字符串的数值。

可以通过操作,可以直接分离出内容

1
2
3
4
str := "0123456"
fmt.Println(str[0:3]) // 012
fmt.Println(str[3:]) // 3456
fmt.Println(str[:3]) // 012

不允许对字符串中间的内容做任何修改。

1
.\main.go:24:9: cannot assign to str[2]

转移方式:

\x11ff 十六进制
\o1188 八进制

原生字符串,一般用于制作正则表达式。使用反引号。

1
2
const GoUsage=`Go is a tool for managing xxx
xxx... `

unicode字符集,参考主页:http://unicode.org

golang使用的是utf32或者UCS-4,每个码点占用32bit。

utf-8

ascii部分是使用1个byte,常用字使用2或3个字符来表示。

解析的时候,读取高位来判断是采取几个字节(二进制)来加载。

高位为0,就是ascii,使用1个byte

0xxxxxxx

高位为110,需要使用2个byte

110xxxxx 10xxxxxx

高位为1110,需要使用3个byte

1110xxxx 10xxxxxx 10xxxxxx

高位为11110,需要使用4个byte

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

这样能完全兼容ascii码,而且能支持字符的压缩。

unicode/utf8包中,RuneCountInString能量出来utf8字符长度。

string相关的包 bytes,strings,strconv,unicode

strings包,主要提供查找、替换、比较、阶段、拆分和合并功能;

bytes包,用于处理[]byte的库,类似于strings。由于string是不能修改的,所以一般加工字符串的时候,都通过转换成[]byte来做处理。

string和[]byte的转换。

1
2
bytes := []byte("I am byte array !")
str := string(bytes)

strconv包,用于将布尔,整型,浮点与string之间做转换;
unicode包,用于处理utf8相关操作。

中文处理

1
2
3
4
5
6
7
8
9
10
	"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"

func transcoding(input []byte, from, to string) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(input), simplifiedchinese.GBK.NewDecoder())
return io.ReadAll(reader)
}

body, err := transcoding(r.Body, "gbk2312", "utf-8")
rootDoc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))

const

测试实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package learngotest

import (
"fmt"
"testing"
)

type Priority int

const (
LOG_EMERG Priority = iota
LOG_ALERT
)

func TestLogiota(t *testing.T) {
fmt.Println(LOG_EMERG)
fmt.Println(LOG_ALERT)
}

const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexStarving
mutexWaiterShift = iota
starvationThresholdNs = 1e6
)

func TestLockiota(t *testing.T) {
fmt.Println(mutexLocked)
fmt.Println(mutexWoken)
fmt.Println(mutexStarving)
fmt.Println(mutexWaiterShift) // 3
fmt.Println(starvationThresholdNs)
}

const (
bit0, mask0 = 1 << iota, 1<<iota - 1
bit1, mask1
_, _
bit3, mask3
)

func TestBitiota(t *testing.T) {
fmt.Println(bit0, mask0) // 1,0
fmt.Println(bit1, mask1) // 2,1
fmt.Println(bit3, mask3) // 8,7
}

复合数据类型

数组

数组一般都是指定长度的某种类型 。由于长度无法变化,其实很少使用到。

1
2
3
4
5
var a[3] int
var b [3]int = [3]int{1, 2, 3} // 初始化
c := [...]int{1, 2, 3}//简略方式
r := [...]int {99:-1} // 初始化了100个元素,100号位置写-1,其他全部都写0
keys := make([]string, 0, len(domains))

数组能直接做==的检测判断。

在调用函数的时候,将会赋值给函数的内部变量,如果参数使用了数组,将会存在内存拷贝。如果传递大的数组,将会有性能消耗。而且在修改数组内容时,也是在复制的数组中修改。如果想减少消耗,并且支持同步修改,还是需要使用数组指针方式来传递。

slice

切片是可变长的数组。每个元素都是相同类型。它包含了指针、长度和容量。指针是指向自己持有的slice的第一个元素,不一定是真实的数组中的第一个。长度,就是当前slice的长度。容积是全部长度。slice可以通过切片方法,分离出子串。其实本质上,他们还是指向同一个数组的。只是由于他们的指针位置不同,而体现出来不一样。

切片的方法a[n:m]将会取出[n,m)之间的元素出来,产生新的slice。
注意数字就是使用的下表,从0开始
如果n没有填写,默认意思是0。
m没有填写,默认就是切片的长度。
常见用法:
a[:]获取全部内容。
a[:0]清理掉全部的成员=[0,0)
s = s[:len(s)-1]抓取最后一个元素;
s = s[len(s)-1:]删除最后一个元素;

1
2
3
make([]T,len,[cap])

// 测试

append函数为slice追加内容。如果cap足够,那就直接添加到尾部,否则将重新创数组,将老数组copy进去,将新的内容添加到数组尾部。append都是返回一个新的数组出来,而不会去直接修改老数组。

slice类似这样的数据结构:

1
2
3
4
type IntSlice struct {
ptr *int
len,cap int
}

在使用 slice 的时候,需要注意底层存在一个数组。如果能复用,将会大大的提高运行效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
var s []int
s = make([]int, 0)
for i := 0; i < 10; i++ {
s = append(s, i)
}
fmt.Printf("1 %#v\n", s)

// 去掉头
s = s[1:]
fmt.Println(s)

// 取出第一个
s = s[0:1]
fmt.Printf("2 %#v\n", s)

// 清空
s = s[:0]
fmt.Printf("3 %#v\n", s)

for i := 0; i < 10; i++ {
s = append(s, i)
}

// 最后一个元素
s = s[len(s)-1:]
fmt.Printf("4 %#v\n", s)
}

需要做一次内存拷贝。如果不在乎顺序,可以直接将最后位置的元素,直接赋值掉指定位置的元素,删除slice最后的元素。

在stackoverflow里面的建议是直接使用连接两个拆分的slice,这样反而会比较高效。

原文地址

1
2
3
func remove(slice []int, s int) []int {
return append(slice[:s], slice[s+1:]...)
}

slice在循环的时候,删除一个元素golang slice delete while iterating

2018-11-13golang slice遍历删除-go语言中文网

2016-07-25Modifying a Go slice in-place during iteration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var x = []int{90, 15, 81, 87, 47, 59, 81, 18, 25, 40, 56, 8}

i := 0
l := len(x)
for i < l {
if x[i] % 2 != 0 {
x = append(x[:i], x[i+1:]...)
l--
} else {
i++
}
}
x = x[:i]

fmt.Println(x)
// [90 18 40 56 8]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

/*slice 遍历删除示例*/
func main() {
//定义一个数字切片
ageList := []int{1, 3, 7, 7, 8, 2, 5}

//遍历删除6以下的
for i := 0; i < len(ageList); {
if ageList[i] < 6 {
ageList = append(ageList[:i], ageList[i+1:]...)
} else {
i++
}
}
fmt.Printf("after del:%v", ageList)
}

输出 after del:[7 7 8]

stackoverflow中的例子

获取高赞的处理方法,第一遍是将全部有用节点排列到<i的数组里面,[i,j)之间的内存都做一次强制的设置成空。s = s[:i]就是将slice区域重置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i := 0 // output index
for _, x := range s {
if isValid(x) {
// copy and increment index
s[i] = x
i++
}
}
// Prevent memory leak by erasing truncated values
// (not needed if values don't contain pointers, directly or indirectly)
for j := i; j < len(s); j++ {
s[j] = nil
}
s = s[:i]

注意,这将在底层数组中的索引i之后保留旧值,因此如果值是或包含指针,这将泄漏内存,直到片本身被垃圾收集。您可以通过将所有值设置为nil或从i到切片结束的零值来解决此问题,然后再截断切片。这个警告适用于说第二步骤需要小心。

连接两个slice,使用可变长函数。

1
2
3
4
5
func TestSliceJoin(lst ...int)[]int {
ageList := []int{1, 3, 7, 7, 8, 2, 5}
ageList = append(ageList,lst...)
return ageList
}

检查某个元素是否在其中

Golang 1.18 引入了 slices 包,它提供了一组方便的函数来操作切片,使得常见的切片操作更加简洁和高效。以下是一些 slices 包中的主要功能:

  1. Contains: 检查切片是否包含某个元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    numbers := []int{0, 42, 10, 8}
    containsTen := slices.Contains(numbers, 10) // true
    fmt.Println(containsTen)
    }
  2. Equal: 检查两个切片是否相等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    c := []int{1, 2, 4}
    fmt.Println(slices.Equal(a, b)) // true
    fmt.Println(slices.Equal(a, c)) // false
    }
  3. Clone: 创建一个切片的副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    a := []int{1, 2, 3}
    b := slices.Clone(a)
    fmt.Println(b) // [1, 2, 3]
    }
  4. Index: 查找某个元素在切片中的索引。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    numbers := []int{0, 42, 10, 8}
    index := slices.Index(numbers, 10) // 2
    fmt.Println(index)
    }
  5. Replace: 替换切片中的一部分内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    a := []int{1, 2, 3, 4, 5}
    b := []int{9, 9, 9}
    slices.Replace(a, 1, 4, b...)
    fmt.Println(a) // [1, 9, 9, 9, 5]
    }
  6. Sort: 对切片进行排序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    numbers := []int{4, 2, 3, 1}
    slices.Sort(numbers)
    fmt.Println(numbers) // [1, 2, 3, 4]
    }
  7. BinarySearch: 在已排序的切片中进行二分查找。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    numbers := []int{1, 2, 3, 4, 5}
    index, found := slices.BinarySearch(numbers, 3)
    fmt.Println(index, found) // 2 true
    }
  8. Delete: 删除切片中的一个元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    "slices"
    )

    func main() {
    a := []int{1, 2, 3, 4, 5}
    a = slices.Delete(a, 1, 3)
    fmt.Println(a) // [1, 4, 5]
    }

这些函数使得操作切片变得更加方便和高效,减少了手动编写重复代码的需要。如果你需要更多信息,可以参考官方文档了解更多细节。

map

key/value方式无序的集合。golang中的map其实就是哈希表。其中key的类型需要支持==比较运算符。浮点型最好不好当成key。

创建map的方法

1
2
3
4
5
ages :=make(map[string]int)
ages := map[string]int {
"alice" : 31,
"Charlie" : 34,
}

map的元素可以通过key下标来访问

1
2
ages["alice"]=32
fmt.Println(ages["alice"])

通过delete对map中的元素删除,即使不存在也能删除。

1
delete(ages,"alice")

在循环map的时候,删除keys是否安全。Is it safe to remove selected keys from map within a range loop?

stackoverflow实例map在遍历的时候删除

effective_go

1
2
3
4
5
for key := range m {
if key.expired() {
delete(m, key)
}
}

也能直接对不存在的元素直接做操作

1
ages["bob"] = ages["bob"]+1

系统会自动初始化key为bob的元素并且返回0

map中的元素不是变量,所以无法取得地址。原因也是由于随着元素的删减,可能内存空间有放大,或者缩小。所以取得的内存地址可能失效。

循环方式是

1
2
3
for name,age := range ages {
fmt.Printf("%s\t%d\n",name,age)
}

由于是hash所以遍历的顺序是不固定的。map变量定义出来之后都是nil值。可以通过make语句来创建初始化。

如需要检查一个元素是否有初始化,可以通过

1
2
3
4
age, ok := ages["bob"]
if !ok {
// 不存在元素。
}

map容器不能直接做比较操作。

go语言没有提供set类型,可以使用map来代替。

(golang-sort-map-by-key)[https://aguidehub.com/blog/2022-11-11-golang-sort-map-by-key/]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"sort"
)

func main() {
domains := map[string]int{"infinitbility": 1, "aguidehub": 2,
"sortoutcode": 3, "gorepairhub": 4}

keys := make([]string, 0, len(domains))

for key := range domains {
keys = append(keys, key)
}

sort.Strings(keys)
// Sort map by keys in descending order
// sort.Sort(sort.Reverse(sort.StringSlice(keys)))

for _, k := range keys {
fmt.Println(k, domains[k])
}
}

结构体

结构体里面可以定义0-n个成员。

1
2
3
4
type T struct {
a,b int
}
val := &T{a:1,b:2}

如果函数需要修改结构体内容,出于对效率的考虑,一般都是使用结构体的引用作为参数来传递。

结构体的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
type Point struct {
X,Y int
}
type Circle struct {
Center Point
Radius int
}
type Wheel struct {
Circle Circle
Spokes int // 辐条
}
var w Wheel
w.Circle.Center.X = 8

go语言中能再struct中定义匿名的成员,这样就直接将其他的struct直接嵌入到了自己的结构体

1
2
3
4
5
6
7
8
9
10
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
var w Wheel
w.X = 9

struct可以通过

1
fmt.Printf("%#v\n",w)

输出信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 初始化对象的写法
type QueryRequest struct {
Cmd QueryCmd `protobuf:"varint,1,opt,name=cmd,proto3,enum=chainnode.QueryCmd" json:"cmd,omitempty"`
Paras []string `protobuf:"bytes,2,rep,name=paras,proto3" json:"paras,omitempty"`
Extension map[string]string `protobuf:"bytes,3,rep,name=extension,proto3" json:"extension,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func main() {
// 创建一个 QueryRequest 实例
request := &QueryRequest{
Cmd: CmdQueryTransaction, // 设置查询命令为查询交易信息
Paras: []string{"123456"}, // 设置参数为字符串数组 ["123456"]
Extension: map[string]string{ // 设置扩展字段为键值对字典 {"key1": "value1", "key2": "value2"}
"key1": "value1",
"key2": "value2",
},
}

16进制数据转换

1
2
3
4
5
6
data, err := hex.DecodeString(txHash)
if err != nil {
return nil, fmt.Errorf("getBTCTxTransfer %v", err.Error())
}
var req chainhash.Hash
copy(req[:],data)

在使用第三方库的时候,一定要仔细看它们提供的源码,里面应该有一些提供了给你用的函数,直接省了太多时间。

将时间字符串转换成time.Time

需要注意定制一下自己的时区

1
2
3
timeZone := "Asia/Shanghai"
loc, _ := time.LoadLocation(timeZone)
dateTime, err := time.ParseInLocation("2006-01-02 15:04:05", str, loc)

如何对比 time.Time 变量的值

很容易犯的错误

1
2
3
4
// Compare compares the time instant t with u. If t is before u, it returns -1;
// if t is after u, it returns +1; if they're the same, it returns 0.
func (t Time) Compare(u Time) int {
}

gb2312 -> utf8

1
2
3
4
5
6
7
8
9
10
import iconv "github.com/djimenez/iconv-go"
func ConvertGB2312ToUtf8(str string) string {
// 扩大倍数,否则会存在截断的问题
out := make([]byte, len(str)*2)
out = out[:]
iconv.Convert([]byte(str), out, "gb2312", "utf-8//IGNORE")

utf8str := string(out)
return utf8str
}

从通道中读取相同内容的问题,尤其是在涉及 slice []string 的情况下,可能是因为所有 goroutine 共享和修改了同一个切片实例,而没有进行正确的拷贝或同步。要理解这个问题,我们需要详细了解切片在 Go 中是如何工作的,以及它们在并发环境中的行为。

切片共享问题

在 Go 中,切片本质上是一个指向底层数组的引用。因此,当多个 goroutine 共享同一个切片时,如果一个 goroutine 修改了该切片的内容,其他 goroutine 会看到这些修改。以下是一些常见原因和解决方法:

  1. 共享切片实例:如果多个 goroutine 使用同一个切片实例并对其进行修改,那么它们会看到相同的内容。

  2. 没有进行拷贝:在向通道发送切片时,如果只是发送了切片的引用而没有进行深拷贝,那么接收方会看到切片的最新状态,而不是发送时的状态。

示例与解决方案

假设我们有一个通道,用于传递 []string 类型的切片。以下示例演示了可能出现的问题和如何解决它。

错误示例

下面的示例展示了共享切片导致的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"time"
)

func worker(ch chan []string) {
for {
data := <-ch
fmt.Println("Received:", data)
}
}

func main() {
ch := make(chan []string)

// 启动一个worker goroutine
go worker(ch)

// 创建一个共享的切片
sharedSlice := []string{"a", "b", "c"}

// 发送切片到通道
for i := 0; i < 3; i++ {
sharedSlice[0] = fmt.Sprintf("a%d", i) // 修改切片内容
ch <- sharedSlice // 发送切片
time.Sleep(1 * time.Second)
}
}

输出可能如下:

1
2
3
Received: [a2 b c]
Received: [a2 b c]
Received: [a2 b c]

你会发现接收到的内容是相同的,这是因为发送的是同一个切片实例,每次修改都影响到了之前发送的切片。

解决方案:深拷贝切片

为了避免这个问题,可以在发送切片之前进行深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"time"
)

func worker(ch chan []string) {
for {
data := <-ch
fmt.Println("Received:", data)
}
}

func main() {
ch := make(chan []string)

// 启动一个worker goroutine
go worker(ch)

// 创建一个共享的切片
sharedSlice := []string{"a", "b", "c"}

// 发送切片到通道
for i := 0; i < 3; i++ {
sharedSlice[0] = fmt.Sprintf("a%d", i) // 修改切片内容
// 深拷贝切片
sliceCopy := make([]string, len(sharedSlice))
copy(sliceCopy, sharedSlice)
ch <- sliceCopy // 发送拷贝的切片
time.Sleep(1 * time.Second)
}
}

输出将如下所示:

1
2
3
Received: [a0 b c]
Received: [a1 b c]
Received: [a2 b c]

这样每次发送到通道的是切片的一个副本,接收方不会受到后续修改的影响。

总结

当从通道中读取相同内容时,可能是因为切片在并发环境中被共享和修改。解决方法是发送切片的副本,而不是发送切片的引用。通过在发送到通道之前进行深拷贝,可以确保接收方接收到的是发送时的状态,而不是后续修改的状态。

实际操作

golang中如何将一个小数转换成字符串,最多显示6位,如果精度只有2位,就只显示2位?

在 Go 中,如果你想将一个小数转换成字符串并控制最多显示 6 位小数,但当精度只有 2 位时只显示 2 位,可以使用 fmt 包中的 fmt.Sprintf 或 strconv.FormatFloat 方法来格式化浮点数。

这里是具体的实现方式:

方法 1:使用 fmt.Sprintf

fmt.Sprintf 提供了灵活的格式化选项。你可以通过 %.6f 格式限定最多显示 6 位小数,同时结合 strings.TrimRight 和 strings.TrimSuffix 去除多余的零。

package main

import (
“fmt”
“strings”
)

func FormatFloat(f float64) string {
s := fmt.Sprintf(“%.6f”, f) // 格式化为 6 位小数
s = strings.TrimRight(s, “0”) // 去掉末尾的多余零
s = strings.TrimSuffix(s, “.”) // 如果最后是小数点,去掉小数点
return s
}

func main() {
fmt.Println(FormatFloat(123.456)) // 输出 “123.456”
fmt.Println(FormatFloat(123.4)) // 输出 “123.4”
fmt.Println(FormatFloat(123.000001)) // 输出 “123.000001”
fmt.Println(FormatFloat(123.00)) // 输出 “123”
}

方法 2:使用 strconv.FormatFloat

strconv.FormatFloat 允许你指定浮点数的格式化规则。你可以使用 strconv.FormatFloat 来控制小数点后的精度。

package main

import (
“fmt”
“strconv”
)

func FormatFloat(f float64) string {
// 使用 ‘f’ 格式,保留最多 6 位小数,但删除无效的零
return strconv.FormatFloat(f, ‘f’, -1, 64)
}

func main() {
fmt.Println(FormatFloat(123.456)) // 输出 “123.456”
fmt.Println(FormatFloat(123.4)) // 输出 “123.4”
fmt.Println(FormatFloat(123.000001)) // 输出 “123.000001”
fmt.Println(FormatFloat(123.00)) // 输出 “123”
}

方法对比

•    fmt.Sprintf 的方式可以让你灵活地控制格式化后删除尾随的零和小数点。
•    strconv.FormatFloat 自动去除无效的零,但你无法精确指定最大显示的小数位数。

总结

•    如果你希望显示最多 6 位小数,并在必要时去除无效的尾零和小数点,fmt.Sprintf 是一个非常灵活的选择。
•    strconv.FormatFloat 也能自动去除无效的尾零并保留必要的精度。